#include "FileUtils.h"
#include <string.h>
#include<iostream>
#include<string.h>
#include<sys/stat.h>
#include<unistd.h>
#include"Exception.h"
// #ifdef __MINGW32
std::list<FileInfo> FileUtils::scanDir(const char * path)  {
    std::list<FileInfo> list;
    DIR * dir = opendir(path);
    dirent *dir_t;
    FileInfo info;
    struct stat statbuf;
    char fullPath[FILENAME_MAX];
    if (dir == NULL) {
        std::string msg = strerror(errno);
        msg.append(":");
        msg.append(path);
        throw Exception(msg);
    }
    
    while ( (dir_t = readdir(dir)) ) {
        if ( !strcmp(".", dir_t->d_name) || !strcmp("..", dir_t->d_name)) {
            continue;
        }
        memset(fullPath, 0, FILENAME_MAX);
        sprintf(fullPath, "%s/%s", path, dir_t->d_name);
        if (stat(fullPath, &statbuf) != 0) {
            std::cerr << fullPath << ":" << strerror(errno) << std::endl;
            throw Exception("Get file stat failed");
        }
        info.name = dir_t->d_name;
        if ( S_ISDIR(statbuf.st_mode) ) {
            info.type = FileUtils::fileType::dir;
            info.size = -1;
            list.push_front(info);
        } else {
            info.type = FileUtils::fileType::file;
            info.size = statbuf.st_size;
            list.push_back(info);
        }
    }
    if ( closedir(dir) != 0) {
        Exception("Close dir failed");
    }
    return list;
}
// #endif


std::list<FileInfo> FileUtils::traverseDir(const char * path) {
    std::list<FileInfo> once,res;
    std::list<std::string> dirs;
    std::string cur_path;
    once = res = FileUtils::scanDir(path);
    for (auto i = once.begin(); i != once.end(); i++) {
        if (i->type == FileUtils::fileType::dir) {
            dirs.push_back(i->name);
        }
    }
    int count = 0;
    while ( !dirs.empty() ) {
        cur_path = *dirs.begin();
        once = FileUtils::scanDir( (std::string(path) + "/" + cur_path).c_str());
        for (auto i = once.begin(); i != once.end(); i++) {
            i->name = cur_path + "/" + i->name;
            res.push_back(*i);
            if (i->type == FileUtils::fileType::dir) {
                dirs.push_back(i->name);
            }
        }
        
        dirs.pop_front();
        count++;
    }
    return res;
    
}

std::string FileUtils::toSizeString(size_t size) {
    std::string company;
    double s;
    size_t head,tail;
    if (size > 1073741824) {
        company = "GiB";
        s = size/1073741824.0;
    } else if (size > 1048576) {
        company = "MiB";
        s = size/1048576.0;
    } else if (size > 1024) {
        company = "KiB";
        s = size / 1024.0;
    } else {
        company = "Byte";
        s = size;
    }
    head = s;
    tail = ((int)(s*100))%100;
    return std::to_string(head) + "." + std::to_string(tail) + company;
}

bool FileUtils::mkdirs(const char * str)  {
    Path path(str);
    auto pathlist = path.getPathList();
    std::string str_path;
    size_t size = pathlist.size();
    for (size_t i = 0; i < size; i++) {
        str_path += "/";
        str_path += pathlist[i];
        #ifdef __MINGW32__
        if (access(str_path.c_str(), F_OK) == -1 && mkdir(str_path.c_str()) == -1) {
            std::string msg = strerror(errno);
            msg.append(": ");msg.append(str_path);
            throw Exception(msg);
        }
        #else
        if (access(str_path.c_str(), F_OK) == -1 && mkdir(str_path.c_str(), 0775) == -1) {
            std::string msg = strerror(errno);
            msg.append(": ");msg.append(str_path);
            throw Exception(msg);
        }
        #endif
    }
    return true;
}

FileInfo::FileInfo(std::string name, int type, long long size) {
    this->name = name;
    this->type = type;
    this->size = size;
}
void FileInfo::loadFromLocal(const std::string &filePath) {
    loadFromLocal(filePath.c_str());
}
void FileInfo::loadFromLocal(const char * filePath) {
    struct stat statbuf;
    if ( stat(filePath, &statbuf) == -1 ) {
        throw FileException(std::string(strerror(errno)) + ":" + filePath);
    }
    Path path(filePath);
    this->size = statbuf.st_size;
    this->name = path[path.size() - 1];
    this->type = S_ISDIR(statbuf.st_mode) ? FileUtils::dir : FileUtils::file;
}

FileInfo FileInfo::getFromLocal(const std::string &filePath) {
    return getFromLocal(filePath.c_str());
}
FileInfo::FileInfo(){}

FileInfo FileInfo::getFromLocal(const char * filePath) {
    FileInfo fi;
    fi.loadFromLocal(filePath);
    return fi;
}

bool FileInfo::isDir() {
    return type == FileUtils::dir;
}

bool FileInfo::isFile() {
    return type == FileUtils::file;
}

std::string FileInfo::toString() {
    char buffer[8192];
    snprintf(buffer, 8192, "%d %lld %s", this->type, this->size, this->name.c_str());
    return std::string(buffer);
}

std::string FileInfo::toStandardStr() {
    char buffer[8192];
    snprintf(buffer, 8192, "%-4s %-10s %s", this->type == FileUtils::fileType::dir ? "DIR" : "FILE", this->size > 0 ? FileUtils::toSizeString(this->size).c_str() : "-1", this->name.c_str());
    return std::string(buffer);
}

void FileInfo::parse(const char * str, FileInfo& info)  {
    auto len = strlen(str);
    size_t pos[2], cnt = 0;
    for (size_t i = 0; i < len && cnt < 2; i++) {
        if (str[i] == ' '){
            pos[cnt++] = i;
        }
    }
    if (cnt != 2 || pos[1] == (len - 1) ) {
        throw Exception(std::string("file info format error:") + str);
    }
    info.type = atoi(str);
    info.size = atoll(str + pos[0] + 1);
    info.name = str + pos[1] + 1;
}

std::string FileInfo::getSuffix() {
    return getSuffix(name);
}

std::string FileInfo::getSuffix(std::string &name) {
    int pos = name.find_last_of(".");
    if (pos == -1) {
        if ( (pos = name.find_last_of("/")) == -1) {
            return name;
        } else {
            return name.substr(pos+1);
        }
    } else {
        return name.substr(pos+1);
    }
}

Path::Path() {

}
Path::Path(const char * path) {
    this->append(path);
}

const std::vector<std::string>& Path::getPathList() {
    return this->path_list;
}


bool Path::append(const char * path) {
    size_t len = strlen(path);
    if (len == 0) return true;
    size_t pos = 0;
    const char *pos_p = strchr(path, '/');
    if (pos_p == path) {
        this->append(path + 1);
        return true;
    }
    // 无'/'时直接整个插入到路径
    if (pos_p == NULL) {
        if (!strcmp("..", path)) {
            if(this->path_list.size() > 0) this->path_list.pop_back();
        } else {
            this->path_list.push_back(path);
        }
    } else {
        // 定位到'/'第一次出现的下标
        pos = pos_p - path;

        // 转换为std::sting类型
        std::string str = std::string(path, pos );

        // 两点为上一级
        if (str == "..") {
            if (!this->path_list.empty()) {
                this->path_list.pop_back();
            }
        } else {
            this->path_list.push_back(str);
        }
        this->append(path + pos + 1);
    }
    return true;
}

std::string Path::getPath() {
    size_t s = this->path_list.size();
    std::string str;
    if (s == 0) {
        str = "/";
        return str;
    }
    for (size_t i = 0; i < s; i++) {
        str.append("/");
        str.append(this->path_list[i]);
    }
    return str;
}

void Path::setPath(const char * str) {
    this->path_list.clear();
    this->append(str);
}

int Path::size() {
    return this->path_list.size();
}

const std::string& Path::operator[](int i)  {
    if(i > ((int)this->path_list.size() - 1) || i < 0) {
        throw Exception("out of range");
    }
    return this->path_list[i];
}

FileException::FileException(std::string msg):Exception(msg) {}